use std::cmp::Ordering;

use itertools::Itertools;
use ndarray::Array2;
use ndarray::Axis;
use num_complex::ComplexFloat;
use serde::Serialize;
use ts_rs::TS;

use super::indicators::IndicatorGroupKey;
use super::kline_flat::KlineFlat;
use super::kline_flat::ValueWrapper;
use super::kline::Klt;
use super::table::Table;
use anyhow::Result;
use ndarray::Array1;
use ndarray_stats::CorrelationExt;

#[derive(Serialize, TS)]
#[ts(export)]
pub struct CorrelationMatrix {
    /// matrix， 矩阵的行列长度必须等于indicators.len()
    pub matrix: Vec<Vec<f64>>,
    /// indicators names: adx_adx, adx_di_plus, adx_di_minus, aroon_up, aroon_down,
    /// Refer to [Table](crate::model::table::Table)
    pub indicator_items: Vec<String>,
}

impl CorrelationMatrix {
    /// 获取矩阵单元格的值
    pub fn get_cross_cell_values(&self, indicator_group_keys: &Vec<IndicatorGroupKey>) -> Vec<f64> {
        let mut values = Vec::new();

        // Generate all unique pairs from the indicators slice
        for i in 0..indicator_group_keys.len() {
            let row_prefix = indicator_group_keys[i].to_string();
            for j in (i + 1)..indicator_group_keys.len() {
                let col_prefix = indicator_group_keys[j].to_string();
                for (r, rname) in self.indicator_items.iter().enumerate() {
                    for (c, cname) in self.indicator_items.iter().enumerate() {
                        if rname.starts_with(&row_prefix) && cname.starts_with(&col_prefix) {
                            values.push(self.matrix[r][c]);
                        }
                    }
                }
            }
        }

        values
    }

    /// 根据最大绝对值，获取矩阵单元格的值
    pub fn get_cross_cell_value_by_max_abs_value(
        &self,
        indicator_group_keys: &Vec<IndicatorGroupKey>,
    ) -> f64 {
        let mut cell_value = 0.0;

        // Generate all unique pairs from the indicators slice
        for i in 0..indicator_group_keys.len() {
            let row_prefix = indicator_group_keys[i].to_string();
            for j in (i + 1)..indicator_group_keys.len() {
                let col_prefix = indicator_group_keys[j].to_string();
                for (r, rname) in self.indicator_items.iter().enumerate() {
                    for (c, cname) in self.indicator_items.iter().enumerate() {
                        if rname.starts_with(&row_prefix) && cname.starts_with(&col_prefix) {
                            if self.matrix[r][c].abs() > cell_value.abs() {
                                cell_value = self.matrix[r][c];
                            }
                        }
                    }
                }
            }
        }

        cell_value
    }
    /// Retrieves the top 'count' combinations of indicator groups with the smallest minimum cross-cell values.
    pub fn find_min_max_abs_value_combinations(
        &self,
        indicator_group_keys: &Vec<IndicatorGroupKey>,
        combination_size: usize,
        count: usize,
    ) -> Vec<IndicatorComb> {
        let mut min_max_abs_values = Vec::with_capacity(count);

        for combo in indicator_group_keys.iter().combinations(combination_size) {
            let combo_gks = combo.into_iter().cloned().collect();
            let cell_value = self.get_cross_cell_value_by_max_abs_value(&combo_gks);

            // Determine the correct insertion position
            let position = match min_max_abs_values.binary_search_by(|existing: &IndicatorComb| {
                existing
                    .cell_value
                    .abs()
                    .partial_cmp(&cell_value.abs())
                    .unwrap_or(Ordering::Less)
            }) {
                Ok(pos) | Err(pos) => pos,
            };

            // Manage the list size and insert the new combination if it fits criteria
            if min_max_abs_values.len() < count || position < count {
                if min_max_abs_values.len() == count {
                    min_max_abs_values.pop();
                }
                min_max_abs_values.insert(
                    position,
                    IndicatorComb {
                        gks: combo_gks,
                        cell_value,
                        ..Default::default()
                    },
                );
            }
        }

        min_max_abs_values
    }
}
// Function to rank elements of an Array1
fn rank_array(arr: &Array1<f64>) -> Array1<f64> {
    let mut sorted_with_indices: Vec<_> = arr.indexed_iter().collect();
    sorted_with_indices.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
    //
    let mut ranks = vec![0.0; arr.len()];
    for (rank, (_, &val)) in sorted_with_indices.iter().enumerate() {
        // Find and assign the rank to the correct position in the ranks vector
        for (idx, &original_val) in arr.iter().enumerate() {
            if (val - original_val).abs() < f64::EPSILON {
                ranks[idx] = (rank as f64) + 1.0;
                break;
            }
        }
    }
    //
    // Convert the ranks vector into an Array1
    Array1::from_vec(ranks)
}

/// To compute correlations between KlineFlat items based on their indicators' values,
/// we'll first need to extract and rank specific indicators for each Kline item, then compute the correlation matrix.
///
/// Given a list of KlineFlat items and a list of indicator names, this function prepares
/// the data, computes the ranked values for each indicator across all KlineFlat items,
/// and then computes the correlation matrix for those indicators.
///
/// @param klines: A list of KlineFlat items.
///
/// @param indicator_names: A list of indicator names.
///
/// @return: A matrix of correlation values.
///
/// @note: This function assumes that the input data is valid and that the indicator names are valid.
///
pub fn compute_indicator_item_correlation_matrix(
    klines: &[KlineFlat],
    indicators_to_analyze: &Vec<String>,
) -> Result<CorrelationMatrix> {
    // Initialize a 2D array to hold ranked indicator values
    let mut data_matrix = Vec::with_capacity(klines.len());

    // For each indicator, extract and rank its values across all KlineFlat items
    for indicator_name in indicators_to_analyze {
        let mut indicator_values = Vec::with_capacity(klines.len());
        for kl in klines {
            match kl.map.get(indicator_name) {
                Some(ValueWrapper::Float(value)) => indicator_values.push(*value),
                _ => {
                    // return Err(anyhow::anyhow!(format!(
                    //     "Indicator [{}] not found or not a float.",
                    //     indicator_name
                    // )))
                    indicator_values.push(0.0);
                    println!("Indicator [{}] not found or not a float.", indicator_name);
                }
            }
        }
        // Rank the values for this indicator
        let ranked_values = rank_array(&Array1::from_vec(indicator_values));
        data_matrix.push(ranked_values.to_vec());
    }

    // Convert the vector of vectors into an Array2 for correlation computation
    let data_matrix_2d = Array2::from_shape_vec(
        (indicators_to_analyze.len(), klines.len()),
        data_matrix.concat(),
    )
    .map_err(|e| anyhow::anyhow!(e.to_string()))?;

    // Compute the correlation matrix
    let correlation_matrix = data_matrix_2d
        .pearson_correlation()
        .map_err(|e| anyhow::anyhow!(e.to_string()))?;

    // Convert the correlation matrix to SerializableCorrelationMatrix, using indicators_to_analyze for indicators field
    let serializable_matrix = CorrelationMatrix {
        matrix: correlation_matrix
            .axis_iter(Axis(0))
            .map(|row| row.to_vec())
            .collect(),
        indicator_items: indicators_to_analyze
            .iter()
            .map(|s| s.to_string())
            .collect(),
    };

    Ok(serializable_matrix)
}

pub fn extract_indicator_item_names(table: &Table) -> Result<Vec<String>> {
    match &table.columns {
        Some(columns) => {
            let names: Vec<String> = columns
                .iter()
                .filter_map(|column| {
                    let name = &column.name;
                    if !name.ends_with("_signal0")
                        && !name.ends_with("_signal1")
                        && !name.ends_with("_signal2")
                        && !name.ends_with("_signal3")
                    {
                        Some(name.clone())
                    } else {
                        None
                    }
                })
                .collect();
            Ok(names)
        }
        None => Err(anyhow::anyhow!("Columns not found in the table.")),
    }
}

#[derive(Serialize, Debug, TS, Clone, Default)]
#[ts(export)]
pub struct IndicatorComb {
    /// group key e.g. ADX, AROON, MACD
    pub gks: Vec<IndicatorGroupKey>,

    /// correlation matrix max value
    pub cell_value: f64,
}

impl IndicatorComb {
    pub fn id_from(&self, security_id: &str, klt: &Klt) -> Result<String> {
        let gks_names = self
            .gks
            .iter()
            .map(|gk| gk.to_string())
            .collect::<Vec<_>>()
            .join("_");
        Ok(format!("{}.{}.{}", security_id, klt, gks_names))
    }
}

// Usage:
// let nd_array: Array2<f64> = /* Initialize your array */;
// let serializable = SerializableCorrelationMatrix::from(nd_array);
#[cfg(test)]
mod test {
    use std::collections::HashMap;

    use chrono::TimeZone;
    use chrono::Utc;
    use ndarray::arr2;
    use ndarray::aview2;
    use ndarray::stack;
    use ndarray::Array1;
    use ndarray::Axis;
    // use ndarray_linalg::Eig; // cause link error
    use ndarray_stats::CorrelationExt;

    use crate::model::indicators::IndicatorGroupKey;
    use crate::model::kline_flat::KlineFlat;
    use crate::model::kline_flat::ValueWrapper;
    use crate::model::kline::Klt;
    use crate::model::matrix::rank_array;
    use crate::model::matrix::IndicatorComb;

    use super::compute_indicator_item_correlation_matrix;
    use super::CorrelationMatrix;

    #[tokio::test]
    async fn test_pearson_correlation() {
        let a = arr2(&[[1., 3., 5.], [2., 4., 6.]]);
        let covariance = a.cov(1.).unwrap();
        println!("covariance: {:?}", covariance);
        assert_eq!(covariance, aview2(&[[4., 4.], [4., 4.]]));

        let a = arr2(&[[1., 3., 5.], [2., 4., 6.]]);
        let corr = a.pearson_correlation().unwrap();
        println!("corr = {:?}", corr);
        let epsilon = 1e-7;
        assert!(corr.abs_diff_eq(&arr2(&[[1., 1.], [1., 1.],]), epsilon));
    }

    // will cause link error
    // #[test]
    // fn test_ndarray_linalg_corrcoef() {
    //     let a: Array2<f64> = array![
    //         [-1.01, 0.86, -4.60, 3.31, -4.81],
    //         [3.98, 0.53, -7.04, 5.29, 3.55],
    //         [3.30, 8.26, -3.89, 8.20, -1.51],
    //         [4.43, 4.96, -7.66, -7.33, 6.18],
    //         [7.31, -6.43, -6.16, 2.47, 5.58],
    //     ];

    //     // Compute eigenvalues and eigenvectors
    //     let (eigs, vecs) = a.eig().unwrap();

    //     // Filter out any negligible imaginary parts to treat as real (assuming symmetry)
    //     let real_eigs: Vec<f64> = eigs.iter()
    //         .map(|c| if c.im.abs() < 1e-10 { c.re } else { panic!("Non-negligible imaginary part found, suggesting non-symmetric behavior or numerical instability.") })
    //         .collect();
    //     let real_vecs = vecs.mapv(|c| Complex::new(c.re, 0.0)); // Assuming you want vectors as real valued arrays

    //     // Demonstrative check (replace with appropriate assertions or logic)
    //     println!("{:?}", real_eigs);
    //     println!("{:?}", real_vecs);
    // }

    //
    #[test]
    fn test_rank() {
        let data1 = Array1::from_vec(vec![1.0, 2.0, 3.0, 6.0, 2.0, 4.0, 5.0]);
        let ranked_data1 = rank_array(&data1);
        println!("{:?}", ranked_data1);

        let data2 = Array1::from_vec(vec![1.0, 2.0, 3.0, 6.0, 2.0, 4.0, 5.0]);
        let ranked_data2 = rank_array(&data2);
        println!("{:?}", ranked_data2);

        //
        // At this point, you would implement the Pearson correlation using the ranked data
        // as described in the previous example or find another method to calculate Spearman's rho.
        // Note: Direct Spearman correlation isn't provided by `ndarray-stats` out-of-the-box.

        let a = stack(Axis(0), &[(&ranked_data1).into(), (&ranked_data2).into()]).unwrap();
        let corr = a.pearson_correlation().unwrap();
        println!("corr = {:?}", corr);
    }

    #[test]
    fn test_compute_indicator_item_correlation_matrix() {
        // Assuming `klines` is a vector of KlineFlat objects and `indicators_to_analyze` is a list of indicator names you want to correlate
        // Create a dummy dataset
        let klines = vec![
            KlineFlat {
                id: None,
                security_id: "SH.STK.600519".to_string(),
                klt: Klt::M005, // Assuming Klt enum is defined with variants like FiveMinutes
                klts: Utc.ymd(2023, 4, 1).and_hms(0, 0, 0),
                open: 100.0,
                high: 110.0,
                low: 95.0,
                close: 105.0,
                volume: 1000,
                idc_score: Some(0.8),
                map: {
                    let mut m = HashMap::new();
                    m.insert("adx_adx".to_string(), ValueWrapper::Float(25.3));
                    m.insert("adx_di_plus".to_string(), ValueWrapper::Float(40.1));
                    m.insert("adx_di_minus".to_string(), ValueWrapper::Float(15.2));
                    m
                },
                last_updated: None,
            },
            KlineFlat {
                id: None,
                security_id: "SH.STK.600519".to_string(),
                klt: Klt::M005,
                klts: Utc.ymd(2023, 4, 1).and_hms(0, 5, 0),
                open: 105.0,
                high: 115.0,
                low: 100.0,
                close: 110.0,
                volume: 1500,
                idc_score: Some(0.9),
                map: {
                    let mut m = HashMap::new();
                    m.insert("adx_adx".to_string(), ValueWrapper::Float(6.5));
                    m.insert("adx_di_plus".to_string(), ValueWrapper::Float(42.0));
                    m.insert("adx_di_minus".to_string(), ValueWrapper::Float(14.5));
                    m
                },
                last_updated: None,
            },
            // Add more KlineFlat items as needed...
        ];

        let indicator_items_to_analyze = vec![
            "adx_adx".to_string(),
            "adx_di_plus".to_string(),
            "adx_di_minus".to_string(),
        ];

        match compute_indicator_item_correlation_matrix(&klines, &indicator_items_to_analyze) {
            Ok(CorrelationMatrix {
                matrix,
                indicator_items: indicators,
            }) => {
                // Print the correlation matrix with friendly labels
                println!("Correlation Matrix: {:?}", matrix);
                for (i, indicator_i) in indicators.iter().enumerate() {
                    println!("Indicator {}: {} = {:?}", i, indicator_i, matrix[i]);
                    for (j, indicator_j) in indicators.iter().enumerate() {
                        let correlation = matrix[i][j];
                        println!("{} ~ {}: {:.2}", indicator_i, indicator_j, correlation);
                    }
                    println!(); // Print a newline for readability between rows
                }
            }
            Err(e) => println!("Error computing correlation matrix: {}", e),
        }
    }

    #[test]
    fn test_get_cross_cell_values_by_min_abs_value() {
        // Create a dummy CorrelationMatrix using the provided data
        //
        let corr_matrix: CorrelationMatrix = CorrelationMatrix {
            // matrix row/col length must be same as indicator_items.len()
            matrix: vec![
                vec![
                    1.0000, -0.1168, 0.0047, 0.0177, -0.0035, 0.1535, -0.1278, -0.2147,
                ],
                vec![
                    -0.1168, 1.0000, 0.0806, 0.1441, 0.4381, 0.2482, -0.3688, -0.3729,
                ],
                vec![
                    0.0047, 0.0806, 1.0000, 0.3724, 0.2987, -0.2637, -0.2513, -0.2855,
                ],
                vec![
                    0.0177, 0.1441, 0.3724, 1.0000, 0.4480, 0.0581, -0.3656, -0.2810,
                ],
                vec![
                    -0.0035, 0.4381, 0.2987, 0.4480, 1.0000, 0.1263, -0.3420, -0.2985,
                ],
                vec![
                    0.1535, 0.2482, -0.2637, 0.0581, 0.1263, 1.0000, 0.1188, 0.1464,
                ],
                vec![
                    -0.1278, -0.3688, -0.2513, -0.3656, -0.3420, 0.1188, 1.0000, 0.9373,
                ],
                vec![
                    -0.2147, -0.3729, -0.2855, -0.2810, -0.2985, 0.1464, 0.9373, 1.0000,
                ],
            ],
            indicator_items: vec![
                "adx_adx".to_string(),
                "adx_di_plus".to_string(),
                "adx_di_minus".to_string(),
                "aroon_up".to_string(),
                "aroon_down".to_string(),
                "aso_dif".to_string(),
                "boll_upper".to_string(),
                "boll_middle".to_string(),
            ],
        };

        // group 1 Test
        let selected_indicators = vec![IndicatorGroupKey::ADX, IndicatorGroupKey::AROON];
        let values = corr_matrix.get_cross_cell_values(&selected_indicators);
        assert_eq!(values.len(), 3 * 2);
        println!("len={}, values={:?}", values.len(), values); // Expected output based on the provided data
        let max_value = corr_matrix.get_cross_cell_value_by_max_abs_value(&selected_indicators);
        assert_eq!(max_value, 0.4381);

        // group 2 Test
        let selected_indicators = vec![
            IndicatorGroupKey::ADX,
            IndicatorGroupKey::AROON,
            IndicatorGroupKey::BOLL,
        ];
        let values = corr_matrix.get_cross_cell_values(&selected_indicators);
        assert_eq!(values.len(), 3 * 4 + 2 * 2);
        println!("len={}, values={:?}", values.len(), values); // Expected output based on the provided data
        let max_value = corr_matrix.get_cross_cell_value_by_max_abs_value(&selected_indicators);
        assert_eq!(max_value, 0.4381);
    }

    #[test]
    fn test_find_min_max_abs_value_combinations() {
        // Create a dummy CorrelationMatrix using the provided data
        //
        let corr_matrix: CorrelationMatrix = CorrelationMatrix {
            // matrix row/col length must be same as indicator_items.len()
            matrix: vec![
                vec![
                    1.0000, -0.1168, 0.0047, 0.0177, -0.0035, 0.1535, -0.1278, -0.2147,
                ],
                vec![
                    -0.1168, 1.0000, 0.0806, 0.1441, 0.4381, 0.2482, -0.3688, -0.3729,
                ],
                vec![
                    0.0047, 0.0806, 1.0000, 0.3724, 0.2987, -0.2637, -0.2513, -0.2855,
                ],
                vec![
                    0.0177, 0.1441, 0.3724, 1.0000, 0.4480, 0.0581, -0.3656, -0.2810,
                ],
                vec![
                    -0.0035, 0.4381, 0.2987, 0.4480, 1.0000, 0.1263, -0.3420, -0.2985,
                ],
                vec![
                    0.1535, 0.2482, -0.2637, 0.0581, 0.1263, 1.0000, 0.1188, 0.1464,
                ],
                vec![
                    -0.1278, -0.3688, -0.2513, -0.3656, -0.3420, 0.1188, 1.0000, 0.9373,
                ],
                vec![
                    -0.2147, -0.3729, -0.2855, -0.2810, -0.2985, 0.1464, 0.9373, 1.0000,
                ],
            ],
            indicator_items: vec![
                "adx_adx".to_string(),
                "adx_di_plus".to_string(),
                "adx_di_minus".to_string(),
                "aroon_up".to_string(),
                "aroon_down".to_string(),
                "aso_dif".to_string(),
                "boll_upper".to_string(),
                "boll_middle".to_string(),
            ],
        };
        // test 1 group
        let min_min_values = corr_matrix.find_min_max_abs_value_combinations(
            &vec![
                IndicatorGroupKey::ADX,
                IndicatorGroupKey::AROON,
                IndicatorGroupKey::ASO,
                IndicatorGroupKey::BOLL,
            ],
            2,
            2,
        );
        println!("found {} Min/Max Values", min_min_values.len());
        for (i, ic) in min_min_values.iter().enumerate() {
            println!("[{}]: {:?}", i, ic);
        }
        let min_min_values = corr_matrix.find_min_max_abs_value_combinations(
            &vec![
                IndicatorGroupKey::ADX,
                IndicatorGroupKey::AROON,
                IndicatorGroupKey::ASO,
                IndicatorGroupKey::BOLL,
            ],
            2,
            40,
        );
        println!("found {} Min/Max Values", min_min_values.len());
        for (i, ic) in min_min_values.iter().enumerate() {
            println!("[{}]: {:?}", i, ic);
        }

        // test 2 group
        let min_min_values = corr_matrix.find_min_max_abs_value_combinations(
            &vec![
                IndicatorGroupKey::ADX,
                IndicatorGroupKey::AROON,
                IndicatorGroupKey::ASO,
                IndicatorGroupKey::BOLL,
            ],
            3,
            2,
        );
        println!("found {} Min/Max Values", min_min_values.len());
        for (i, ic) in min_min_values.iter().enumerate() {
            println!("[{}]: {:?}", i, ic);
        }
        let min_min_values = corr_matrix.find_min_max_abs_value_combinations(
            &vec![
                IndicatorGroupKey::ADX,
                IndicatorGroupKey::AROON,
                IndicatorGroupKey::ASO,
                IndicatorGroupKey::BOLL,
            ],
            3,
            40,
        );
        println!("found {} Min/Max Values", min_min_values.len());
        for (i, ic) in min_min_values.iter().enumerate() {
            println!("[{}]: {:?}", i, ic);
        }

        // ---- model::matrix::test::test_find_min_max_abs_value_combinations stdout ----
        // found 6 Min/Max Values
        // [0]: IndicatorComb { id: None, gks: [AROON, ASO], min_value: 0.1263 }
        // [1]: IndicatorComb { id: None, gks: [ASO, BOLL], min_value: 0.1464 }
        // [2]: IndicatorComb { id: None, gks: [ADX, ASO], min_value: -0.2637 }
        // [3]: IndicatorComb { id: None, gks: [AROON, BOLL], min_value: -0.3656 }
        // [4]: IndicatorComb { id: None, gks: [ADX, BOLL], min_value: -0.3729 }
        // [5]: IndicatorComb { id: None, gks: [ADX, AROON], min_value: 0.4381 }
        // found 4 Min/Max Values
        // [0]: IndicatorComb { id: None, gks: [AROON, ASO, BOLL], min_value: -0.3656 }
        // [1]: IndicatorComb { id: None, gks: [ADX, ASO, BOLL], min_value: -0.3729 }
        // [2]: IndicatorComb { id: None, gks: [ADX, AROON, BOLL], min_value: 0.4381 }
        // [3]: IndicatorComb { id: None, gks: [ADX, AROON, ASO], min_value: 0.4381 }
    }

    #[test]
    fn test_indicator_comb_id_from() {
        let indicator_comb = IndicatorComb {
            gks: vec![
                IndicatorGroupKey::ADX,
                IndicatorGroupKey::AROON,
                IndicatorGroupKey::MACD,
            ],
            ..Default::default()
        };
        assert_eq!(
            indicator_comb.id_from("SZ.KZZ.128136", &Klt::DAY).unwrap(),
            "SZ.KZZ.128136.DAY.adx_aroon_macd"
        );
    }
}
